// System Include
#include <iostream>

// Local Include
#include "GpuInterface.cuh"
#include "CpuInterface.hpp"

#include "Utils.h"
#include "UtilsGPU.cuh"
#include "Config.h"

int main() {
    // Cpu Data
    float *ConvImage_CPU;
    float *ConvTemp_CPU;
    float *ConvResult_CPU;
    float *ConvKernel_CPU;

    {
        std::cout << "Start Cpu Init...";
        // Image Data
        ConvImage_CPU = new float[IMAGE_SIZE];
        Utils::GenRandomArrayR(ConvImage_CPU, IMAGE_SIZE);
        //Utils::GenOneArrayR(ConvImage_CPU, IMAGE_SIZE);

        // Temp Data
        ConvTemp_CPU = new float[IMAGE_SIZE];    

        // Result Data
        ConvResult_CPU = new float[IMAGE_SIZE];

        // Kernel Data
        ConvKernel_CPU = new float[KERNEL_LENGTH];
        Utils::GenRandomArrayR(ConvKernel_CPU, KERNEL_LENGTH);
        //Utils::GenOneArrayR(ConvKernel_CPU, KERNEL_LENGTH);
        std::cout << " Finish!" << std::endl;
    }

    // Gpu Data
    float *ConvImage_GPU;
    float *ConvTemp_GPU;
    float *ConvResult_GPU;
    //float *ConvKernel_GPU; define __constant__ in GpuFunction.cuh

    cudaError_t CudaStatus;

    {
        std::cout << "Start Gpu Init...";
        // Image Data
        Utils::CudaMallocSafe(CudaStatus, (void**)&ConvImage_GPU, sizeof(float) * IMAGE_SIZE);
        Utils::CudaMemcpySafe(CudaStatus, ConvImage_GPU, ConvImage_CPU, sizeof(float) * IMAGE_SIZE);

        // Temp Data
        Utils::CudaMallocSafe(CudaStatus, (void**)&ConvTemp_GPU, sizeof(float) * IMAGE_SIZE);

        // Result Data
        Utils::CudaMallocSafe(CudaStatus, (void**)&ConvResult_GPU, sizeof(float) * IMAGE_SIZE);

        // Kernel Data
        setConvolutionKernel(ConvKernel_CPU);
        std::cout << " Finish!" << std::endl;
    }

    // Gpu Calculate
    std::cout << "Start Gpu Calculation..." << std::endl;
    ConvGPU::SeparableConvX(ConvTemp_GPU, ConvImage_GPU, IMAGE_W, IMAGE_H);
    ConvGPU::SeparableConvY(ConvResult_GPU, ConvTemp_GPU, IMAGE_W, IMAGE_H);
    std::cout << "Finish Gpu Calculation!" << std::endl;

#ifndef PROFILE
    // Cpu Calculate
    std::cout << "Start Cpu Calculation..." << std::endl;
    ConvCPU::SeparableConvX(ConvTemp_CPU, ConvImage_CPU, ConvKernel_CPU, IMAGE_W, IMAGE_H);
    ConvCPU::SeparableConvY(ConvResult_CPU, ConvTemp_CPU, ConvKernel_CPU, IMAGE_W, IMAGE_H);
    std::cout << "Finish Cpu Calculation!" << std::endl;

#ifdef DEBUG
    std::cout << "Start Output Temp... [Debug]" << std::endl;
    Utils::WriteCuda2DArrayToFile(ConvTemp_GPU, nullptr, IMAGE_W, IMAGE_H, "GpuTemp.txt");
    Utils::Write2DArrayToFile(ConvTemp_CPU, IMAGE_W, IMAGE_H, "CpuTemp.txt");
    std::cout << "Finish Output Temp! [Debug]" << std::endl;
#endif // DEBUG

    // Calculate Error
    std::cout << "Start Calculate Error..." << std::endl;
    Utils::CudaMemcpySafe(CudaStatus, ConvTemp_GPU, ConvResult_CPU, sizeof(float) * IMAGE_SIZE);
    float Diff = UtilsGPU::L2NormS(ConvTemp_GPU, ConvResult_GPU, IMAGE_SIZE);
    std::cout << "Finish Calculate Error!" << std::endl;
    std::cout << Diff << std::endl;
#endif // PROFILE
}